/*
**************************************************************************************
*Myriad Engine                                                                       *
*Copyright (C) 2006-2007, 5d Studios (www.5d-Studios.com)                            *
*                                                                                    *
*This library is free software; you can redistribute it and/or                       *
*modify it under the terms of the GNU Lesser General Public                          *
*License as published by the Free Software Foundation; either                        *
*version 2.1 of the License, or (at your option) any later version.                  *
*                                                                                    *
*This library is distributed in the hope that it will be useful,                     *
*but WITHOUT ANY WARRANTY; without even the implied warranty of                      *
*MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU                   *
*Lesser General Public License for more details.                                     *
*                                                                                    *
*You should have received a copy of the GNU Lesser General Public                    *
*License along with this library; if not, write to the Free Software                 *
*Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA      *
**************************************************************************************
*/
package leola.utils;


import static com.sun.xml.internal.ws.org.objectweb.asm.Opcodes.*;

import java.lang.reflect.Method;
import java.net.URLClassLoader;
import java.util.Comparator;

import leola.ast.ClassDeclStmt;
import leola.backend.EvalException;
import leola.backend.interpreter.Interpreter;
import leola.backend.interpreter.LeoTypeConverter;
import leola.types.LeoClass;
import leola.types.LeoFunction;
import leola.types.LeoNativeFunction;
import leola.types.LeoObject;
import leola.types.LeoObject.LeoType;

import com.sun.xml.internal.ws.org.objectweb.asm.ClassWriter;
import com.sun.xml.internal.ws.org.objectweb.asm.Label;
import com.sun.xml.internal.ws.org.objectweb.asm.MethodVisitor;
import com.sun.xml.internal.ws.org.objectweb.asm.Opcodes;
import com.sun.xml.internal.ws.org.objectweb.asm.Type;

/**
 * @author Tony
 *
 */
public class ClassGenerator {

	/**
	 * Temp package name
	 */
	private static final String TEMP_PKG = "_leolatmp_";

	/**
	 * Class loader
	 */
	private ByteArrayClassLoader classLoader;


	/**
	 *
	 */
	public ClassGenerator() {
		this.classLoader = new ByteArrayClassLoader((URLClassLoader)this.getClass().getClassLoader());
	}

	/**
	 * Base class for all LeoClasses that implement an interface.
	 *
	 * @author Tony
	 *
	 */
	public static class LeoAsmBaseClass {
		public Interpreter interpreter;
		public LeoClass leoClass;
		/**
		 * @param interpreter
		 * @param aCls
		 */
		public LeoAsmBaseClass(Interpreter interpreter, LeoClass aCls) {
			this.interpreter = interpreter;
			this.leoClass = aCls;
		}

		/**
		 * Invokes a Leola method from a Java interface.
		 *
		 * @param methodName
		 * @param values
		 * @throws Exception
		 */
		public void invokeMethod(String methodName, Object ... values) throws Exception {
			LeoObject obj = this.leoClass.getProperty(methodName);
			if ( obj == null ) {
				throw new EvalException(this.leoClass.getClassName() + " does not contain the method: " + methodName);
			}

			/* If this is a function, lets execute it */
			if ( obj.isOfType(LeoType.FUNCTION) ) {
				LeoObject[] params = null;
				if ( values != null ) {
					params = new LeoObject[values.length];
					for(int i = 0; i < values.length; i++ ) {
						params[i] = LeoTypeConverter.convertToLeolaType(values[i]);
					}
				}

				LeoFunction function = obj.as();
				this.interpreter.executeFunction(function, params);
			}
			/* Is this a native function? */
			else if ( obj.isOfType(LeoType.NATIVE_FUNCTION)) {
				LeoNativeFunction function = obj.as();
				this.interpreter.executeFunction(function, values);
			}
			else {
				throw new EvalException(methodName + " is not a method: " + obj);
			}
		}
	}

	public class TmpClass extends LeoAsmBaseClass implements Comparator<LeoObject>, Runnable {

		/**
		 * @param interpreter
		 * @param aCls
		 */
		public TmpClass(Interpreter interpreter, LeoClass aCls) {
			super(interpreter, aCls);
		}

		/* (non-Javadoc)
		 * @see java.lang.Runnable#run()
		 */
		@Override
		public void run() {
			//invokeMethod("run", null);
		}

		/* (non-Javadoc)
		 * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object)
		 */
		@Override
		public int compare(LeoObject o1, LeoObject o2) {

			return 0;
		}
	}

	/**
	 * Generates a {@link Class}.
	 *
	 * @param interpreter
	 * @param stmt
	 * @return
	 * @throws Exception
	 */
	public Class<?> generateClass(Interpreter interpreter, ClassDeclStmt stmt) throws Exception {
		byte[] clsData = generateClassForInterface(interpreter, stmt);
		Class<?> aCls = this.classLoader.loadClass(stmt.getClassName(), clsData);
		return aCls;
	}

	/**
	 *
	 * @param stmt
	 * @return
	 */
	public byte[] generateClassForInterface(Interpreter interpreter, ClassDeclStmt stmt) throws Exception {
		String[] interfaces = stmt.getInterfaceNames();

		ClassWriter cw = new ClassWriter(0);

		cw.visit(V1_5, ACC_PUBLIC, stmt.getClassName(), null, toAsmName(LeoAsmBaseClass.class), toAsmName(interfaces) );
		writeConstructor(cw, stmt.getClassName());
//		cw.visitField(ACC_PUBLIC + ACC_FINAL, "interpreter", toAsmDesc(Interpreter.class), null, null).visitEnd();
//		cw.visitField(ACC_PUBLIC + ACC_FINAL, "body", toAsmDesc(Stmt.class), null, null).visitEnd();

		for(String interfaceName : interfaces) {
			generateInterfaceMethod(cw, interfaceName);
		}

		cw.visitEnd();
		byte[] b = cw.toByteArray();
		return b;
	}

	/**
	 * Writes the constructor
	 *
	 * @param cw
	 * @param className
	 */
	private void writeConstructor(ClassWriter cw, String className) {
		String clsDesc = toAsmDesc(className);

		MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "<init>", "(Lleola/utils/ClassGenerator;Lleola/backend/interpreter/Interpreter;Lleola/types/LeoClass;)V", null, null);
		mv.visitCode();
		Label l0 = new Label();
		mv.visitLabel(l0);
//		mv.visitLineNumber(132, l0);
		mv.visitVarInsn(ALOAD, 0);
		mv.visitVarInsn(ALOAD, 1);
		mv.visitFieldInsn(PUTFIELD, "leola/utils/ClassGenerator$TmpClass", "this$0", "Lleola/utils/ClassGenerator;");
		Label l1 = new Label();
		mv.visitLabel(l1);
//		mv.visitLineNumber(131, l1);
		mv.visitVarInsn(ALOAD, 0);
		mv.visitVarInsn(ALOAD, 2);
		mv.visitVarInsn(ALOAD, 3);
		mv.visitMethodInsn(INVOKESPECIAL, "leola/utils/ClassGenerator$LeoAsmBaseClass", "<init>", "(Lleola/backend/interpreter/Interpreter;Lleola/types/LeoClass;)V");
		mv.visitInsn(RETURN);
		Label l2 = new Label();
		mv.visitLabel(l2);
		mv.visitLocalVariable("this", clsDesc, null, l0, l2, 0);
		mv.visitLocalVariable("interpreter", "Lleola/backend/interpreter/Interpreter;", null, l0, l2, 2);
		mv.visitLocalVariable("aCls", "Lleola/types/LeoClass;", null, l0, l2, 3);
		mv.visitMaxs(3, 4);
		mv.visitEnd();
	}

	/**
	 * Write the interface method
	 *
	 * @param cw
	 * @param method
	 */
	private void writeInterfaceMethod(ClassWriter cw, Method method) {


		MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, method.getName()
										, Type.getMethodDescriptor(method)
										, null
										, toAsmDesc(method.getExceptionTypes()));
		mv.visitCode();
		writeMethodExceptionType(cw, method);
		/* TODO - implement the method body
		 * call this.invokeMethod( "methodName", params );
		 */
//		mv.visitInsn(Opcodes.NOP);


		mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL,
				toAsmName(LeoAsmBaseClass.class), "invokeMethod", "(Ljava/lang/String;[Ljava/lang/Object;)V");

		mv.visitInsn( Opcodes.RETURN );
		mv.visitMaxs(1, 1);
		mv.visitEnd();
	}

	/**
	 * Writes the method exception types
	 *
	 * @param cw
	 * @param method
	 */
	private void writeMethodExceptionType(ClassWriter cw, Method method) {

	}

	/**
	 * Generates the interface methods.
	 *
	 * @param cw
	 * @param interfaceName
	 * @throws Exception
	 */
	private void generateInterfaceMethod(ClassWriter cw, String interfaceName) throws Exception {
		Class<?> anInter = Class.forName(interfaceName);
		Method[] methods = anInter.getMethods();
		for(Method method : methods) {
			writeInterfaceMethod(cw, method);
		}
	}



	/**
	 * To the ASM description name
	 * @param aCls
	 * @return
	 */
	public static String toAsmDesc(Class<?> aCls) {
		return "L" + toAsmName(aCls) + ";";
	}

	/**
	 * To the ASM description name
	 * @param aCls
	 * @return
	 */
	public static String toAsmDesc(String aCls) {
		return "L" + toAsmName(aCls) + ";";
	}

	/**
	 * To the ASM description name
	 *
	 * @param aCls
	 * @return
	 */
	public static String[] toAsmDesc(Class<?>[] aCls) {
		String[] result = new String[aCls.length];
		for(int i = 0; i < aCls.length; i++) {
			result[i] = toAsmName(aCls[i]);
		}
		return result;
	}

	/**
	 * Translates to an ASM name
	 *
	 * @param names
	 * @return
	 */
	public static String[] toAsmName(String[] names) {
		String[] result = new String[names.length];
		for(int i = 0; i < names.length; i++) {
			result[i] = toAsmName(names[i]);
		}

		return result;
	}

	/**
	 * Translates to an ASM name
	 * @param name
	 * @return
	 */
	public static String toAsmName(String name) {
		return name.replace(".", "/");
	}

	/**
	 * Translates to an ASM name
	 *
	 * @param aCls
	 * @return
	 */
	public static String toAsmName(Class<?> aCls) {
		return toAsmName(aCls.getName());
	}
}
